// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2020, LomboTech Co.Ltd.
 * Author: lomboswer <lomboswer@lombotech.com>
 *
 * Support raph for lombo platforms
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 */

#include <linux/platform_device.h>
#include "interrupt_support.h"
#include "pvrsrv_device.h"
#include "syscommon.h"
#include "vz_support.h"
#include "allocmem.h"
#include "sysinfo.h"
#include "sysconfig.h"
#include "physheap.h"
#if defined(SUPPORT_ION)
#include "ion_support.h"
#endif
#if defined(LINUX)
#include <linux/dma-mapping.h>
#endif
#include "rgx_bvnc_defs_km.h"
#include "rgxdevice.h"
#include "lombo_init.h"

/*
 * In systems that support trusted device address protection, there are three
 * physical heaps from which pages should be allocated:
 * - one heap for normal allocations
 * - one heap for allocations holding META code memory
 * - one heap for allocations holding secured DRM data
 */

#define PHYS_HEAP_IDX_GENERAL     0
#define PHYS_HEAP_IDX_FW          1

#if defined(SUPPORT_TRUSTED_DEVICE)
#define PHYS_HEAP_IDX_TDFWCODE    2
#define PHYS_HEAP_IDX_TDSECUREBUF 3
#elif defined(SUPPORT_DEDICATED_FW_MEMORY)
#define PHYS_HEAP_IDX_FW_MEMORY   2
#endif

/*
 * CPU to Device physical address translation
 */
static
void UMAPhysHeapCpuPAddrToDevPAddr(IMG_HANDLE hPrivData,
				   IMG_UINT32 ui32NumOfAddr,
				   IMG_DEV_PHYADDR *psDevPAddr,
				   IMG_CPU_PHYADDR *psCpuPAddr)
{
	PVR_UNREFERENCED_PARAMETER(hPrivData);

	/* Optimise common case */
	psDevPAddr[0].uiAddr = psCpuPAddr[0].uiAddr;
	if (ui32NumOfAddr > 1) {
		IMG_UINT32 ui32Idx;

		for (ui32Idx = 1; ui32Idx < ui32NumOfAddr; ++ui32Idx)
			psDevPAddr[ui32Idx].uiAddr = psCpuPAddr[ui32Idx].uiAddr;
	}
}

/*
 * Device to CPU physical address translation
 */
static
void UMAPhysHeapDevPAddrToCpuPAddr(IMG_HANDLE hPrivData,
				   IMG_UINT32 ui32NumOfAddr,
				   IMG_CPU_PHYADDR *psCpuPAddr,
				   IMG_DEV_PHYADDR *psDevPAddr)
{
	PVR_UNREFERENCED_PARAMETER(hPrivData);

	/* Optimise common case */
	psCpuPAddr[0].uiAddr = psDevPAddr[0].uiAddr;
	if (ui32NumOfAddr > 1) {
		IMG_UINT32 ui32Idx;

		for (ui32Idx = 1; ui32Idx < ui32NumOfAddr; ++ui32Idx)
			psCpuPAddr[ui32Idx].uiAddr = psDevPAddr[ui32Idx].uiAddr;
	}
}

static PHYS_HEAP_FUNCTIONS gsPhysHeapFuncs = {
	/* pfnCpuPAddrToDevPAddr */
	UMAPhysHeapCpuPAddrToDevPAddr,
	/* pfnDevPAddrToCpuPAddr */
	UMAPhysHeapDevPAddrToCpuPAddr,
	/* pfnGetRegionId */
	NULL,
};

static PVRSRV_ERROR PhysHeapsCreate(PHYS_HEAP_CONFIG **ppasPhysHeapsOut,
				    IMG_UINT32 *puiPhysHeapCountOut)
{
	/*
	 * This function is called during device initialisation, which on Linux,
	 * means it won't be called concurrently. As such, there's no need to
	 * protect it with a lock or use an atomic variable.
	 */
	IMG_UINT32 ui32HeapIDBase = 0;
	PHYS_HEAP_CONFIG *pasPhysHeaps;
	IMG_UINT32 uiHeapCount = 2;

#if defined(SUPPORT_TRUSTED_DEVICE)
	uiHeapCount += 2;
#elif defined(SUPPORT_DEDICATED_FW_MEMORY)
	uiHeapCount += 1;
#endif

	pasPhysHeaps = OSAllocZMem(sizeof(*pasPhysHeaps) * uiHeapCount);
	if (!pasPhysHeaps)
		return PVRSRV_ERROR_OUT_OF_MEMORY;

	pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].ui32PhysHeapID =
		ui32HeapIDBase + PHYS_HEAP_IDX_GENERAL;
	pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].pszPDumpMemspaceName = "SYSMEM";
	pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].eType = PHYS_HEAP_TYPE_UMA;
	pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].psMemFuncs = &gsPhysHeapFuncs;

	pasPhysHeaps[PHYS_HEAP_IDX_FW].ui32PhysHeapID =
		ui32HeapIDBase + PHYS_HEAP_IDX_FW;
	pasPhysHeaps[PHYS_HEAP_IDX_FW].pszPDumpMemspaceName = "SYSMEM_FW";
	pasPhysHeaps[PHYS_HEAP_IDX_FW].eType = PHYS_HEAP_TYPE_UMA;
	pasPhysHeaps[PHYS_HEAP_IDX_FW].psMemFuncs = &gsPhysHeapFuncs;

#if defined(SUPPORT_TRUSTED_DEVICE)
#define TDFWNAME "TDFWCODEMEM"
#define TDBFNAME "TDSECUREBUFMEM"
	pasPhysHeaps[PHYS_HEAP_IDX_TDFWCODE].ui32PhysHeapID =
		ui32HeapIDBase + PHYS_HEAP_IDX_TDFWCODE;
	pasPhysHeaps[PHYS_HEAP_IDX_TDFWCODE].pszPDumpMemspaceName = TDFWNAME;
	pasPhysHeaps[PHYS_HEAP_IDX_TDFWCODE].eType = PHYS_HEAP_TYPE_UMA;
	pasPhysHeaps[PHYS_HEAP_IDX_TDFWCODE].psMemFuncs = &gsPhysHeapFuncs;

	pasPhysHeaps[PHYS_HEAP_IDX_TDSECUREBUF].ui32PhysHeapID =
		ui32HeapIDBase + PHYS_HEAP_IDX_TDSECUREBUF;
	pasPhysHeaps[PHYS_HEAP_IDX_TDSECUREBUF].pszPDumpMemspaceName = TDBFNAME;
	pasPhysHeaps[PHYS_HEAP_IDX_TDSECUREBUF].eType = PHYS_HEAP_TYPE_UMA;
	pasPhysHeaps[PHYS_HEAP_IDX_TDSECUREBUF].psMemFuncs = &gsPhysHeapFuncs;

#elif defined(SUPPORT_DEDICATED_FW_MEMORY)
#define DFWNAME "DEDICATEDFWMEM"
	pasPhysHeaps[PHYS_HEAP_IDX_FW_MEMORY].ui32PhysHeapID =
		ui32HeapIDBase + PHYS_HEAP_IDX_FW_MEMORY;
	pasPhysHeaps[PHYS_HEAP_IDX_FW_MEMORY].pszPDumpMemspaceName = DFWNAME;
	pasPhysHeaps[PHYS_HEAP_IDX_FW_MEMORY].eType = PHYS_HEAP_TYPE_UMA;
	pasPhysHeaps[PHYS_HEAP_IDX_FW_MEMORY].psMemFuncs = &gsPhysHeapFuncs;
#endif

	ui32HeapIDBase += uiHeapCount;

	*ppasPhysHeapsOut = pasPhysHeaps;
	*puiPhysHeapCountOut = uiHeapCount;

	return PVRSRV_OK;
}

static void PhysHeapsDestroy(PHYS_HEAP_CONFIG *pasPhysHeaps)
{
	OSFreeMem(pasPhysHeaps);
}

static void SysDevFeatureDepInit(PVRSRV_DEVICE_CONFIG *psDevConfig,
				 IMG_UINT64 ui64Features)
{
#if defined(SUPPORT_AXI_ACE_TEST)
		if (ui64Features & RGX_FEATURE_AXI_ACELITE_BIT_MASK)
			psDevConfig->eCacheSnoopingMode	=
				PVRSRV_DEVICE_SNOOP_CPU_ONLY;
		else
#endif
			psDevConfig->eCacheSnoopingMode	=
				PVRSRV_DEVICE_SNOOP_NONE;
}

PVRSRV_ERROR SysDevInit(void *pvOSDevice, PVRSRV_DEVICE_CONFIG **ppsDevConfig)
{
	PVRSRV_DEVICE_CONFIG *psDevConfig;
	RGX_DATA *psRGXData;
	RGX_TIMING_INFORMATION *psRGXTimingInfo;
	PHYS_HEAP_CONFIG *pasPhysHeaps;
	IMG_UINT32 uiPhysHeapCount;
	PVRSRV_ERROR eError;
	int iIrq;
	struct resource *psDevMemRes = NULL;
	struct platform_device *psDev;

	psDev = to_platform_device((struct device *)pvOSDevice);

#if defined(LINUX)
	dma_set_mask(pvOSDevice, DMA_BIT_MASK(40));
#endif

	psDevConfig = OSAllocZMem(sizeof(*psDevConfig) +
				  sizeof(*psRGXData) +
				  sizeof(*psRGXTimingInfo));
	if (!psDevConfig)
		return PVRSRV_ERROR_OUT_OF_MEMORY;

	psRGXData = (RGX_DATA *)
		    ((IMG_CHAR *)psDevConfig + sizeof(*psDevConfig));
	psRGXTimingInfo = (RGX_TIMING_INFORMATION *)
			((IMG_CHAR *)psRGXData + sizeof(*psRGXData));

	eError = PhysHeapsCreate(&pasPhysHeaps, &uiPhysHeapCount);
	if (eError)
		goto ErrorFreeDevConfig;

	/* Setup RGX specific timing data */
	psRGXTimingInfo->ui32CoreClockSpeed    = RGX_LOMBO_CORE_CLOCK_SPEED;
	psRGXTimingInfo->bEnableActivePM       = IMG_FALSE;
	psRGXTimingInfo->bEnableRDPowIsland    = IMG_FALSE;
	psRGXTimingInfo->ui32ActivePMLatencyms =
			SYS_RGX_ACTIVE_POWER_LATENCY_MS;

	/* Set up the RGX data */
	psRGXData->psRGXTimingInfo = psRGXTimingInfo;

#if defined(SUPPORT_TRUSTED_DEVICE)
	psRGXData->bHasTDFWCodePhysHeap = IMG_TRUE;
	psRGXData->uiTDFWCodePhysHeapID =
		pasPhysHeaps[PHYS_HEAP_IDX_TDFWCODE].ui32PhysHeapID;

	psRGXData->bHasTDSecureBufPhysHeap = IMG_TRUE;
	psRGXData->uiTDSecureBufPhysHeapID =
		pasPhysHeaps[PHYS_HEAP_IDX_TDSECUREBUF].ui32PhysHeapID;

#elif defined(SUPPORT_DEDICATED_FW_MEMORY)
	psRGXData->bHasFWMemPhysHeap = IMG_TRUE;
	psRGXData->uiFWMemPhysHeapID = PHYS_HEAP_IDX_FW_MEMORY;
#endif

	/* Setup the device config */
	psDevConfig->pvOSDevice		= pvOSDevice;
	psDevConfig->pszName		= "lombo";
	psDevConfig->pszVersion		= NULL;
	psDevConfig->pfnSysDevFeatureDepInit = SysDevFeatureDepInit;

	/* Device setup information */
	psDevMemRes = platform_get_resource(psDev, IORESOURCE_MEM, 1);
	if (psDevMemRes) {
		psDevConfig->sRegsCpuPBase.uiAddr = psDevMemRes->start;
		psDevConfig->ui32RegsSize =
			(unsigned int)(psDevMemRes->end - psDevMemRes->start);
	} else {
		PVR_DPF((PVR_DBG_WARNING, "%s: platform_get_resource failed",
			__func__));
		psDevConfig->sRegsCpuPBase.uiAddr = LOMBO_GPU_PBASE;
		psDevConfig->ui32RegsSize         = LOMBO_GPU_SIZE;
	}

	iIrq = platform_get_irq(psDev, 0);
	if (iIrq >= 0) {
		psDevConfig->ui32IRQ  = (IMG_UINT32)iIrq;
	} else {
		PVR_DPF((PVR_DBG_WARNING, "%s: platform_get_irq failed (%d)",
			__func__, -iIrq));
		psDevConfig->ui32IRQ = LOMBO_IRQ_GPU;
	}

	psDevConfig->pasPhysHeaps		= pasPhysHeaps;
	psDevConfig->ui32PhysHeapCount		= uiPhysHeapCount;

	psDevConfig->aui32PhysHeapID[PVRSRV_DEVICE_PHYS_HEAP_GPU_LOCAL] =
		pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].ui32PhysHeapID;
	psDevConfig->aui32PhysHeapID[PVRSRV_DEVICE_PHYS_HEAP_CPU_LOCAL] =
		pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].ui32PhysHeapID;
	psDevConfig->aui32PhysHeapID[PVRSRV_DEVICE_PHYS_HEAP_EXTERNAL] =
		pasPhysHeaps[PHYS_HEAP_IDX_GENERAL].ui32PhysHeapID;
	psDevConfig->aui32PhysHeapID[PVRSRV_DEVICE_PHYS_HEAP_FW_LOCAL] =
		pasPhysHeaps[PHYS_HEAP_IDX_FW].ui32PhysHeapID;

	psDevConfig->eBIFTilingMode = geBIFTilingMode;
	psDevConfig->pui32BIFTilingHeapConfigs = gauiBIFTilingHeapXStrides;
	psDevConfig->ui32BIFTilingHeapCount =
		ARRAY_SIZE(gauiBIFTilingHeapXStrides);

	/* No power management */
	psDevConfig->pfnPrePowerState       = NULL;
	psDevConfig->pfnPostPowerState      = NULL;

	psDevConfig->bHasFBCDCVersion31      = IMG_FALSE;

	/* No clock frequency */
	psDevConfig->pfnClockFreqGet        = NULL;

	psDevConfig->hDevData               = psRGXData;

#if !defined(NO_HARDWARE)
	psDevConfig->hSysData = RgxLomboInit(psDevConfig);
	if (!psDevConfig->hSysData) {
		psDevConfig->pvOSDevice = NULL;
		return PVRSRV_ERROR_OUT_OF_MEMORY;
	}
#endif

	/* Setup other system specific stuff */
#if defined(SUPPORT_ION)
	IonInit(NULL);
#endif

	*ppsDevConfig = psDevConfig;

	return PVRSRV_OK;

ErrorFreeDevConfig:
	OSFreeMem(psDevConfig);
	return eError;
}

void SysDevDeInit(PVRSRV_DEVICE_CONFIG *psDevConfig)
{
#if defined(SUPPORT_ION)
	IonDeinit();
#endif

#if !defined(NO_HARDWARE)
	RgxLomboDeinit(psDevConfig->hSysData);
#endif

	PhysHeapsDestroy(psDevConfig->pasPhysHeaps);
	OSFreeMem(psDevConfig);
}

PVRSRV_ERROR SysInstallDeviceLISR(IMG_HANDLE hSysData,
				  IMG_UINT32 ui32IRQ,
				  const IMG_CHAR *pszName,
				  PFN_LISR pfnLISR,
				  void *pvData,
				  IMG_HANDLE *phLISRData)
{
	PVR_UNREFERENCED_PARAMETER(hSysData);
	PVR_UNREFERENCED_PARAMETER(ui32IRQ);
	PVR_UNREFERENCED_PARAMETER(pszName);
	PVR_UNREFERENCED_PARAMETER(pfnLISR);
	PVR_UNREFERENCED_PARAMETER(pvData);
	PVR_UNREFERENCED_PARAMETER(phLISRData);

	return OSInstallSystemLISR(phLISRData, ui32IRQ, pszName, pfnLISR,
				   pvData, SYS_IRQ_FLAG_TRIGGER_DEFAULT);
}

PVRSRV_ERROR SysUninstallDeviceLISR(IMG_HANDLE hLISRData)
{
	PVR_UNREFERENCED_PARAMETER(hLISRData);

	return OSUninstallSystemLISR(hLISRData);
}

PVRSRV_ERROR SysDebugInfo(PVRSRV_DEVICE_CONFIG *psDevConfig,
				DUMPDEBUG_PRINTF_FUNC *pfnDumpDebugPrintf,
				void *pvDumpDebugFile)
{
	PVR_UNREFERENCED_PARAMETER(psDevConfig);
	PVR_UNREFERENCED_PARAMETER(pfnDumpDebugPrintf);
	PVR_UNREFERENCED_PARAMETER(pvDumpDebugFile);
	return PVRSRV_OK;
}

